/*
 * Copyright (C) 2019 ~ 2022 Uniontech Software Technology Co.,Ltd.
 *
 * Author:     yanghongwei <yanghongwei@uniontech.com>
 *
 * Maintainer: yanghongwei <yanghongwei@uniontech.com>
 */

#include "inotify.h"
#include "abstractnotifyprocess.h"

#include <iostream>
#include <thread>
#include <algorithm>
#include <time.h>

#include <errno.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/inotify.h>
#include <unistd.h>
#include <string.h>

/**
 * @brief 打印inotify events
 * @param events
 */
void printInotifyEvents(uint32_t events)
{
    if (events & IN_ACCESS)
        std::cout << "IN_ACCESS: ";
    if (events & IN_ATTRIB)
        std::cout << "IN_ATTRIB: ";
    if (events & IN_CLOSE_WRITE)
        std::cout << "IN_CLOSE_WRITE: ";
    if (events & IN_CLOSE_NOWRITE)
        std::cout << "IN_CLOSE_NOWRITE: ";
    if (events & IN_CREATE)
        std::cout << "IN_CREATE: ";
    if (events & IN_DELETE)
        std::cout << "IN_DELETE: ";
    if (events & IN_DELETE_SELF)
        std::cout << "IN_DELETE_SELF: ";
    if (events & IN_MODIFY)
        std::cout << "IN_MODIFY: ";
    if (events & IN_MOVE_SELF)
        std::cout << "IN_MOVE_SELF: ";
    if (events & IN_MOVED_FROM)
        std::cout << "IN_MOVED_FROM: ";
    if (events & IN_MOVED_TO)
        std::cout << "IN_MOVED_TO: ";
    if (events & IN_OPEN)
        std::cout << "IN_OPEN: ";

    std::cout << std::endl;
}

/**
 * @brief getPath 获取路径，最后一个字符为'/'
 * @param path 一个路径
 * @return 最后一个字符为'/'的路径
 */
std::string getPath(const std::string &path)
{
    // 为空返回""
    if (path.length() == 0) {
        return "";
    }

    std::string tmpPath = path;
    if (tmpPath.at(tmpPath.length() - 1) != '/') {
        tmpPath += "/";
    }
    return tmpPath;
}

INotify::INotify()
    : m_running(false)
    , m_fd(-1)
{
}

INotify::~INotify()
{
    if (m_fd != -1) {
        close(m_fd);
    }
}

/**
 * @brief inotify的初始化
 * @return true:初始化成功 false:初始化失败
 */
bool INotify::init()
{
    if (m_fd != -1) {
        return true;
    }

    m_fd = inotify_init1(IN_NONBLOCK);
    return m_fd != -1;
}

/**
 * @brief 添加一个要监听的节点
 * @param node 监听节点
 * @return true:添加成功 false:添加失败
 */
bool INotify::addNode(const NotifyNode &node)
{
    // 未初始化或者文件为空不添加
    if (m_fd == -1 || node.file == "") {
        return false;
    }
    NotifyNode tmp = node;
    tmp.object = nullptr;
    // 根据监听事件类型添加监听
    if (!addPath(tmp.file, getINotifyEvents(tmp))) {
        std::cout << "Cannot watch '" << tmp.file << "':" << strerror(errno) << std::endl;
        return false;
    }

    // 保存监听节点
    if (m_mapNotifyNode.count(tmp) == 0) {
        // value保存的是监听节点被监听次数计数，这里是第一次计数
        m_mapNotifyNode[tmp] = 1;
    } else {
        int total = m_mapNotifyNode[tmp];
        total++; // 计数自增并保存
        m_mapNotifyNode[tmp] = total;
    }

    return true;
}

/**
 * @brief 移除一个已经监听的节点
 * @param node 监听节点
 * @return true:移除成功 false:移除失败
 */
bool INotify::removeNode(const NotifyNode &node)
{
    // 未初始化或者文件为空或者未添加过的节点不做移除操作
    if (m_fd == -1 || node.file == "" || m_mapListenPathWd.count(node.file) == 0) {
        return false;
    }
    NotifyNode tmp = node;
    tmp.object = nullptr;
    // 不存在该监听节点
    if (m_mapNotifyNode.count(tmp) == 0) {
        return false;
    }

    int total = m_mapNotifyNode[tmp];
    // 如果大于一个则只移除计数，不移除真实监听
    if (total > 1) {
        total--;
        m_mapNotifyNode[tmp] = total;
        return true;
    }

    // 移除缓存
    m_mapNotifyNode.erase(tmp);
    removePath(tmp.file);

    return true;
}

/**
 * @brief 启动监听线程
 * @return true 启动成功，false 启动失败
 */
bool INotify::start()
{
    // 未初始化启动失败
    if (m_fd == -1) {
        return false;
    }

    // 已经启动
    if (m_running.load()) {
        return true;
    }

    m_running.store(true);
    return AbstractNotify::start();
}

/**
 * @brief 停止监听线程
 * @return true 停止成功，false 停止失败
 */
bool INotify::stop()
{
    if (m_running.load()) {
        m_running.store(false);
    }

    return true;
}

/**
 * @brief 线程执行体
 */
void INotify::run()
{
    int result = -1; // poll返回值
    nfds_t nfds;
    struct pollfd fds[1];
    /* Prepare for polling. */
    nfds = 1;

    fds[0].fd = m_fd; /* Inotify input */
    fds[0].events = POLLIN; // m_fd有可读数据时触发poll

    /* Wait for events and/or terminal input. */
    std::cout << "Listening for events." << std::endl;
    while (m_running.load()) {
        // 5S超时
        result = poll(fds, nfds, 5000);
        if (result == -1) {
            if (errno == EINTR)
                continue;
            perror("poll");
            m_running.store(false);
            break;
        }

        // 超时继续
        if (result == 0) {
            continue;
        }

        // 判断出发事件是否为POLLIN,如果不是则退出等待下一个事件
        if (!(fds[0].revents & POLLIN)) {
            continue;
        }

        /* Inotify events are available. */
        char buf[4096] __attribute__((aligned(__alignof__(struct inotify_event))));
        ssize_t len;

        /* Loop while events can be read from inotify file descriptor. */
        for (;;) {
            /* Read some events. */
            len = read(m_fd, buf, sizeof(buf));
            if (len == -1 && errno != EAGAIN) {
                perror("read");
                m_running.store(false);
                break;
            }

            /* If the nonblocking read() found no events to read, then
                  it returns -1 with errno set to EAGAIN. In that case,
                  we exit the loop. */
            if (len <= 0)
                break;

            /* Loop over all events in the buffer. */
            handleEvent(buf, len);
        }
    }

    std::cout << "Listening for events stopped." << std::endl;

    /* Close inotify file descriptor. */
    close(m_fd);
    m_fd = -1;
}

/**
 * @brief 线程运行状态
 * @return true:线程在运行 false:线程停止
 */
bool INotify::isRunning()
{
    return m_running.load();
}

/**
 * @brief FileEvent转换为inotify event
 * @param node 监听节点
 * @return inotify events
 */
uint32_t INotify::getINotifyEvents(const NotifyNode &node)
{
    uint32_t events = 0;

    if (node.events & ACCESS) {
        events |= IN_ACCESS;
    }

    if (node.events & ATTRIB) {
        events |= IN_ATTRIB;
    }

    if (node.events & CLOSE_WRITE) {
        events |= IN_CLOSE_WRITE;
    }

    if (node.events & CLOSE_NOWRITE) {
        events |= IN_CLOSE_NOWRITE;
    }

    if (node.events & CREATE) {
        events |= IN_CREATE;
    }

    if (node.events & DELETE) {
        events |= IN_DELETE;
    }

    if (node.events & DELETE_SELF) {
        events |= IN_DELETE_SELF;
    }

    if (node.events & MODIFY) {
        events |= IN_MODIFY;
    }

    if (node.events & MOVE_SELF) {
        events |= IN_MOVE_SELF;
    }

    if (node.events & MOVED_FROM) {
        events |= IN_MOVED_FROM;
    }

    if (node.events & MOVED_TO) {
        events |= IN_MOVED_TO;
    }

    if (node.events & OPEN) {
        events |= IN_OPEN;
    }

    return events;
}

/**
 * @brief inotify event转换为FileEvent
 * @param events inotify events
 * @return FileEvents
 */
uint32_t INotify::getFileNotifyEvents(const uint32_t &events)
{
    uint32_t tmpEvents = 0;

    if (events & IN_ACCESS) {
        tmpEvents |= ACCESS;
    }

    if (events & IN_ATTRIB) {
        tmpEvents |= ATTRIB;
    }

    if (events & IN_CLOSE_WRITE) {
        tmpEvents |= CLOSE_WRITE;
    }

    if (events & IN_CLOSE_NOWRITE) {
        tmpEvents |= CLOSE_NOWRITE;
    }

    if (events & IN_CREATE) {
        tmpEvents |= CREATE;
    }

    if (events & IN_DELETE) {
        tmpEvents |= DELETE;
    }

    if (events & IN_DELETE_SELF) {
        tmpEvents |= DELETE_SELF;
    }

    if (events & IN_MODIFY) {
        tmpEvents |= MODIFY;
    }

    if (events & IN_MOVE_SELF) {
        tmpEvents |= MOVE_SELF;
    }

    if (events & IN_MOVED_FROM) {
        tmpEvents |= MOVED_FROM;
    }

    if (events & IN_MOVED_TO) {
        tmpEvents |= MOVED_TO;
    }

    if (events & IN_OPEN) {
        tmpEvents |= OPEN;
    }

    return tmpEvents;
}

/**
 * @brief inotify事件信号处理
 * @param buff 事件数据缓存
 * @param len 数据长度
 */
void INotify::handleEvent(char *buff, int len)
{
    const struct inotify_event *event;
    for (char *ptr = buff; ptr < buff + len;
         ptr += sizeof(struct inotify_event) + event->len) {
        event = (const struct inotify_event *)ptr;

        /* Print event type. */
        // TODO: 这些之后可以去掉
        printInotifyEvents(event->mask);

        /* Print the name of the watched directory. */
        std::string filePath = m_mapListenWdPath[event->wd];
        /* Print the name of the file. */
        if (event->len) {
            filePath += "/";
            filePath += event->name;
        }

        std::cout << filePath;
        /* Print type of filesystem object. */
        if (event->mask & IN_ISDIR) {
            std::cout << " [directory]" << std::endl;
            // 如果是目录被操作，需要添加监听子目录
            addPath(filePath);
        } else {
            std::cout << " [file]" << std::endl;
        }

        // 获取当前时间
        time_t now = time(NULL);
        // 将inotify events转为filenotify events
        uint32_t fileEvents = getFileNotifyEvents(event->mask);
        // 回调通知事件
        for (auto &callbackFun : mapCallbackFuns) {
            callbackFun.second(now, filePath, "", fileEvents);
        }
    }
}

/**
 * @brief INotify::addPath 添加路径监听
 * @param path 全路径
 * @param events 监听事件(inotify事件类型)
 * @return 监听成功返回true,否则返回false
 */
bool INotify::addPath(const std::string &path, uint32_t events)
{
    if (m_mapListenPathWd.count(path)) {
        return true;
    }

    // 根据监听事件类型添加监听
    int wd = inotify_add_watch(m_fd, path.c_str(), events | getWatchEvents(path));
    if (wd == -1) {
        std::cout << "Cannot watch '" << path << "':" << strerror(errno) << std::endl;
        return false;
    }

    // 保存监听数据
    m_mapListenWdPath[wd] = path;
    m_mapListenPathWd[path] = wd;
    return true;
}

/**
 * @brief INotify::removePath 移除路径监听(连带子目录一起移除)
 * @param path 全路径
 */
void INotify::removePath(const std::string &path)
{
    // 查询所有监听的目录，连带其下的子目录一起取消监听
    for (auto item = m_mapListenPathWd.begin(); item != m_mapListenPathWd.end(); item++) {
        // 是子目录
        if (getPath(item->first).find(getPath(path)) != std::string::npos && isCanRemove(item->first)) {
            inotify_rm_watch(m_fd, item->second);
            // 移除缓存
            m_mapListenWdPath.erase(item->second);
            item = m_mapListenPathWd.erase(item);
            continue;
        }
    }
}

/**
 * @brief INotify::isCanRemove 判断一个路径是否可以移除监听
 * @param path 全路径
 * @return 可以移除返回true,否则返回false
 */
bool INotify::isCanRemove(const std::string &path)
{
    std::string tmpPath = getPath(path);
    // 查询所有监听的节点，判断是否是该节点的监控目录或者其子目录
    for (auto item = m_mapNotifyNode.begin(); item != m_mapNotifyNode.end(); item++) {
        // 是子目录
        if (tmpPath.find(getPath(item->first.file)) != std::string::npos) {
            return false;
        }
    }

    return true;
}

/**
 * @brief INotify::getWatchEvents 获取该路径下的监听事件类型
 * @param path 全路径
 * @return 返回该路径目前监听的inotify事件组
 */
uint32_t INotify::getWatchEvents(const std::string &path)
{
    NotifyNode node;
    node.events = 0;
    std::string tmpPath = getPath(path);
    // 查询所有监听的节点，判断是否是该节点的监控目录或者其子目录
    for (auto item = m_mapNotifyNode.begin(); item != m_mapNotifyNode.end(); item++) {
        // 是子目录
        if (tmpPath.find(getPath(item->first.file)) != std::string::npos) {
            node.events |= item->first.events;
        }
    }
    return getINotifyEvents(node);
}
